全面指南比较Python顶尖HTTP客户端库。学习何时使用Requests、httpx或urllib3,附代码示例及性能洞察。
Python HTTP 客户端揭秘:Requests、httpx 和 urllib3 深度解析
在现代软件开发领域,通信是关键。应用程序很少独立存在;它们主要通过超文本传输协议 (HTTP) 上的 API 与数据库、第三方服务以及其他微服务进行通信。对于 Python 开发者而言,发出这些 HTTP 请求是一项基本任务,而你为此任务选择的库可以显著影响你的生产力、应用程序性能和代码可维护性。
Python 生态系统为此提供了丰富的工具选择,但有三个名字始终脱颖而出:urllib3,稳固的基础;Requests,广受欢迎的标准;以及 httpx,现代且支持异步的竞争者。选择它们并非要找到唯一的“最佳”库,而是要理解它们各自独特的优势,并为你的特定需求选择合适的工具。本指南将提供深入、专业的比较,助你做出明智的决定。
理解基础:什么是 HTTP 客户端?
从本质上讲,HTTP 客户端是旨在向服务器发送 HTTP 请求并处理所接收到的 HTTP 响应的软件。这个简单的定义背后隐藏着大量的复杂性。一个强大的 HTTP 客户端库会处理许多底层细节,包括:
- 管理网络套接字和连接。
- 正确格式化包含请求头、请求体和方法(GET、POST、PUT 等)的 HTTP 请求。
- 处理重定向和超时。
- 管理 cookie 和会话以实现有状态通信。
- 处理不同的内容编码(如 JSON 或表单数据)。
- 处理 SSL/TLS 以实现安全的 HTTPS 连接。
- 重复利用连接以提高性能(连接池)。
尽管 Python 的标准库包含 urllib.request
等模块,但它们通常被认为过于底层和笨重,不适合日常使用。这促使了更强大、用户友好型的第三方库的开发,这些库抽象掉了这种复杂性,让开发者能够专注于其应用程序的逻辑。
经典之选:urllib3
在我们讨论更高级的库之前,了解 urllib3
至关重要。它是 PyPI 上下载量最大的包之一,这并非因为大多数开发者直接使用它,而是因为它是一个强大、可靠的引擎,为无数其他高级库(最著名的是 Requests)提供动力支持。
什么是 urllib3
?
urllib3
是一个强大、注重稳定性的 Python HTTP 客户端。它主要致力于为 HTTP 通信提供可靠高效的基础。它并非像 Requests 那样注重 API 的优雅,而是更侧重于正确性、性能和细粒度控制。
主要特性和优势
- 连接池:这可以说是其最关键的特性。
urllib3
管理连接池。当你向之前联系过的主机发出请求时,它会重用现有连接而不是建立新连接。这显著降低了连续请求的延迟,因为它避免了 TCP 和 TLS 握手的开销。 - 线程安全:单个
PoolManager
实例可以在多个线程之间共享,使其成为多线程应用程序的稳健选择。 - 健壮的错误处理和重试:它提供了复杂的重试失败请求机制,并带有可配置的退避策略,这对于构建与可能不可靠的服务通信的弹性应用程序至关重要。
- 细粒度控制:它暴露了大量的配置选项,允许开发者微调超时、TLS 验证、代理设置等。
- 文件上传:它对多部分表单数据编码有出色的支持,使得高效上传文件变得容易。
代码示例:发起 GET 请求
使用 urllib3
比其高级对应库更为冗长,但仍然直接明了。你通常会与一个 PoolManager
实例进行交互。
import urllib3
import json
# It's recommended to create a single PoolManager instance and reuse it
http = urllib3.PoolManager()
# Define the target URL
url = "https://api.github.com/users/python"
# Make the request
# Note: The request method is passed as a string ('GET')
# The response object is an HTTPResponse instance
response = http.request("GET", url, headers={"User-Agent": "My-Urllib3-App/1.0"})
# Check the response status
if response.status == 200:
# The data is returned as a bytes object and needs to be decoded
data_bytes = response.data
data_str = data_bytes.decode("utf-8")
# Manually parse the JSON
user_data = json.loads(data_str)
print(f"User Name: {user_data['name']}")
print(f"Public Repos: {user_data['public_repos']}")
else:
print(f"Error: Received status code {response.status}")
# The connection is automatically released back to the pool
何时使用 urllib3
- 当你正在构建一个需要发出 HTTP 请求的库或框架,并且希望精细管理依赖时。
- 当你需要极致的性能以及对连接管理和重试逻辑的完全控制时。
- 在需要依赖通常包含(vendored)在其他主要包中的库的遗留系统或受限环境中。
关于 urllib3
的结论
优点:高性能、线程安全、健壮,并对请求生命周期提供深度控制。
缺点:API 冗长且不够直观。对于 JSON 解码和请求参数编码等常见任务,需要手动操作。
大众之选:requests
- “为人设计的 HTTP”
十多年来,requests
一直是 Python 中发起 HTTP 请求的事实标准。其著名的标语“HTTP for Humans”完美概括了其设计理念。它提供了一个优美、简单、优雅的 API,隐藏了由 urllib3
管理的底层复杂性。
什么是 requests
?
requests
是一个高级 HTTP 库,专注于开发者体验和易用性。它以直观的界面封装了 urllib3
的强大功能,使得常见任务变得异常简单,同时在需要时仍可访问强大特性。
主要特性和优势
- 简单优雅的 API:该 API 使用起来非常愉快。发起 GET 请求只需一行可读的代码。
- 会话对象:会话对象是一项基石功能。它们在请求之间持久化参数,自动管理 cookie,最重要的是,底层使用了
urllib3
的连接池。使用Session
是通过requests
实现高性能的推荐方式。 - 内置 JSON 解码:与 JSON API 交互非常简单。响应对象有一个
.json()
方法,可以自动解码响应体并返回 Python 字典或列表。 - 自动内容解压:它透明地处理压缩的响应数据(gzip, deflate),因此你无需考虑。
- 优雅处理复杂数据:发送表单数据或 JSON 有效负载就像将字典传递给
data
或json
参数一样简单。 - 国际域名和 URL:对全球网络提供了卓越的开箱即用支持。
代码示例:发起 GET 请求并处理 JSON
将此示例与 urllib3
版本进行比较。请注意,它无需手动解码或 JSON 解析,更为简洁。
import requests
# The recommended approach for multiple requests to the same host
with requests.Session() as session:
session.headers.update({"User-Agent": "My-Requests-App/1.0"})
url = "https://api.github.com/users/python"
try:
# Making the request is a single function call
response = session.get(url)
# Raise an exception for bad status codes (4xx or 5xx)
response.raise_for_status()
# The .json() method handles decoding and parsing
user_data = response.json()
print(f"User Name: {user_data['name']}")
print(f"Public Repos: {user_data['public_repos']}")
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
何时使用 requests
- 适用于应用程序、脚本和数据科学项目中绝大多数同步 HTTP 任务。
- 与 REST API 交互时。
- 用于快速原型开发和构建内部工具。
- 当你的主要目标是同步网络 I/O 的代码可读性和开发速度时。
需要考虑的局限性
在现代,requests
最大的局限性在于其 API 严格同步。它会阻塞直到收到响应。这使得它不适合基于 asyncio
、FastAPI 或 Starlette 等异步框架构建的高并发应用程序。虽然你可以在线程池中使用它,但对于处理数千个并发连接而言,这种方法不如原生的异步 I/O 效率高。
关于 requests
的结论
优点:使用极其简便、高度可读、功能丰富、社区庞大,且文档出色。
缺点:仅支持同步。对于现代高性能、I/O 密集型应用程序而言,这是一个显著的缺点。
现代竞争者:httpx
- 异步就绪的继任者
httpx
是一个现代、功能齐全的 HTTP 客户端,旨在解决 requests
的局限性,主要是其缺乏异步支持。它被设计为一个下一代客户端,拥抱现代 Python 特性和 Web 协议,同时为来自 requests
的用户提供熟悉的 API。
什么是 httpx
?
httpx
是一个多功能的 Python HTTP 客户端,提供同步和异步 API。其杀手级特性是对 async/await
语法的头等支持。此外,它还带来了对 HTTP/2 和 HTTP/3 等现代 Web 协议的支持,这可以显著提升性能。
主要特性和优势
- 同步和异步支持:这是其标志性特性。你可以使用相同的库和非常相似的 API,既可用于传统的同步脚本,也可用于高性能的异步应用程序。这种统一简化了依赖管理并降低了学习曲线。
- HTTP/2 和 HTTP/3 支持:与
requests
不同,httpx
支持 HTTP/2。该协议允许多路复用——通过单个连接同时发送多个请求和响应——这可以显著加快与支持此协议的现代服务器的通信速度。 - 与
requests
兼容的 API:该 API 被有意设计成在许多情况下可以作为requests
的直接替代品。httpx.get()
等函数和httpx.Client()
(相当于requests.Session()
)等对象会让人立刻感到熟悉。 - 可扩展的传输 API:它拥有一个清晰、定义良好的传输 API,这使得编写用于模拟、缓存或自定义网络协议的自定义适配器变得更加容易。
代码示例:同步、异步和客户端
首先是一个同步示例。请注意它与 requests
代码几乎相同。
# Synchronous httpx code
import httpx
url = "https://api.github.com/users/python-httpx"
with httpx.Client(headers={"User-Agent": "My-HTTPX-App/1.0"}) as client:
try:
response = client.get(url)
response.raise_for_status()
user_data = response.json()
print(f"(Sync) User Name: {user_data['name']}")
print(f"(Sync) Public Repos: {user_data['public_repos']}")
except httpx.RequestError as e:
print(f"An error occurred: {e}")
现在是异步版本。结构相同,但它利用 async/await
来执行非阻塞 I/O。
# Asynchronous httpx code
import httpx
import asyncio
async def fetch_github_user():
url = "https://api.github.com/users/python-httpx"
# Use AsyncClient for async operations
async with httpx.AsyncClient(headers={"User-Agent": "My-HTTPX-App/1.0"}) as client:
try:
# The 'await' keyword pauses execution until the network call completes
response = await client.get(url)
response.raise_for_status()
user_data = response.json()
print(f"(Async) User Name: {user_data['name']}")
print(f"(Async) Public Repos: {user_data['public_repos']}")
except httpx.RequestError as e:
print(f"An error occurred: {e}")
# Run the async function
asyncio.run(fetch_github_user())
何时使用 httpx
- 对于今天开始的任何新项目。其同步/异步双重特性使其成为最通用且面向未来的选择。即使你目前只需要同步请求,使用
httpx
也意味着一旦你的应用程序需求演变,你已准备好无缝过渡到异步。它是涉及现代 Web 框架或需要高并发级别的任何项目的明确选择。 - 当使用 FastAPI、Starlette、Sanic 或 Django 3+ 等异步框架构建应用程序时。
- 当你需要发出大量并发的 I/O 密集型请求时(例如,调用数千个 API)。
- 当你需要与利用 HTTP/2 提升性能的服务器进行通信时。
关于 httpx
的结论
优点:提供同步和异步 API,支持 HTTP/2,设计现代简洁,并为 requests
用户提供熟悉的 API。
缺点:作为一个较年轻的项目,其第三方插件生态系统不如 requests
那么庞大,但正在迅速增长。
功能对比:一览
此摘要快速回顾了这三个库之间的主要区别。
特性:高级、用户友好的 API
- urllib3: 否。底层且冗长。
- requests: 是。这是其主要优势。
- httpx: 是。旨在让
requests
用户感到熟悉。
特性:同步 API
- urllib3: 是。
- requests: 是。
- httpx: 是。
特性:异步 API (async/await
)
- urllib3: 否。
- requests: 否。
- httpx: 是。这是其关键区别。
特性:HTTP/2 支持
- urllib3: 否。
- requests: 否。
- httpx: 是。
特性:连接池
- urllib3: 是。核心功能。
- requests: 是(通过
Session
对象)。 - httpx: 是(通过
Client
和AsyncClient
对象)。
特性:内置 JSON 解码
- urllib3: 否。需要手动解码和解析。
- requests: 是(通过
response.json()
)。 - httpx: 是(通过
response.json()
)。
性能考量
讨论性能时,上下文至关重要。对于单个简单请求,这三个库之间的性能差异可以忽略不计,很可能被网络延迟所掩盖。
性能差异真正显现的地方在于处理并发:
- 多线程环境中的
requests
:这是使用requests
实现并发的传统方式。它可行,但线程具有更高的内存开销,并且可能受到上下文切换成本的影响,尤其当并发任务数量增长到数百甚至数千时。 httpx
结合asyncio
:对于像发起 API 调用这样的 I/O 密集型任务,asyncio
效率要高得多。它使用单个线程和一个事件循环来管理数千个并发连接,且开销极小。如果你的应用程序需要同时查询数百个微服务,httpx
将大大优于线程化的requests
实现。
此外,httpx
对 HTTP/2 的支持可以在与也支持 HTTP/2 的服务器通信时提供额外的性能提升,因为它允许通过相同的 TCP 连接发送多个请求而无需等待响应,从而降低延迟。
为你的项目选择合适的库
基于本次深入探讨,以下是我们向全球开发者提供的实用建议:
如果...,请使用 httpx
你正在启动 2023 年及以后的任何新 Python 项目。其同步/异步双重特性使其成为最通用且面向未来的选择。即使你目前只需要同步请求,使用 httpx
也意味着一旦你的应用程序需求演变,你已准备好无缝过渡到异步。它是涉及现代 Web 框架或需要高并发级别的任何项目的明确选择。
如果...,请使用 requests
你正在处理一个已经广泛使用 requests
的遗留代码库。如果应用程序稳定且没有并发需求,迁移成本可能不值得。它也仍然是简单、一次性脚本的完美选择,在这种情况下,设置异步事件循环的开销是不必要的,并且可读性至关重要。
如果...,请使用 urllib3
你是一个库作者,需要以最少的依赖和最大的控制来发出 HTTP 请求。通过依赖 urllib3
,你可以避免向你的用户强制使用 requests
或 httpx
。如果你的连接或 TLS 管理有非常具体、低层次的要求,而高级库没有暴露这些功能,你也应该选择它。
结论
Python HTTP 客户端领域呈现出一条清晰的演进路径。urllib3
提供了支撑整个生态系统的强大、坚实引擎。requests
在此引擎之上构建,创造了一个如此直观且备受喜爱的 API,使其成为全球标准,为一代 Python 程序员普及了网络访问。如今,httpx
作为现代继任者,保留了 requests
出色的可用性,同时整合了下一代软件所需的关键特性:异步操作和现代网络协议。
对于今天的开发者而言,选择比以往任何时候都更加清晰。尽管 requests
仍然是同步任务的可靠工具,但 httpx
几乎是所有新开发的面向未来的选择。通过理解每个库的优势,你可以自信地选择合适的工具来完成工作,确保你的应用程序健壮、高性能并为未来做好准备。